package excel;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.spire.ms.System.Collections.ArrayList;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import util.ZipUtils;

import java.io.File;
import java.io.FileOutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.text.MessageFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * 导出excel，并与附件打包zip，每条记录用超链接关联附件目录
 */
public class ExcelAttach {

    // zip文件生成根目录
    private static String rootDirLinux = "/opt/export_tmp"; // linux
    private static String rootDirWin = "D:\\export_tmp"; // windows

    public static void main(String[] args) throws Exception {
        exportZipByExcelAndAttach();
    }

    /**
     * 导出zip，将生成的excel和附件一起打包，每条记录的附件目录以超链接(相对路径)跳转
     * https://blog.csdn.net/qq_40563534/article/details/121115169
     * https://www.cnblogs.com/LiZhiW/p/4313789.html
     * https://blog.csdn.net/weixin_42340194/article/details/120772445
     */
    private static void exportZipByExcelAndAttach() throws Exception {
        // 准备数据
        List<Map<String, Object>> list = new ArrayList();

        // 如何创建和初始化一个HashMap https://blog.csdn.net/dengnanhua/article/details/101649909

        // jdk自带集合工具类Collections 单列模式，只有一个元素 产生的是不可变的Map
        // Map<String, String> map = Collections.singletonMap("gh", "1");

        // 使用Guava库 https://blog.csdn.net/qq_44033208/article/details/127314156
        // of方法入参最多只能有 5 对，如果添加的数据超过 5 对，需要改用builder方法
        // b站截取的视频封面，后面的@320w_200h是指定宽高  https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h_!web-space-upload-video.webp
        Map<String, Object> map = ImmutableMap.of("gh", "1", "name", "姓名-001", "deptName", "部门-001",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h"));
        // 不可变，一旦创建就不能再往里添加键值对了
        // 转为可变
        map = Maps.newHashMap(map);
        list.add(map);

        list.add(ImmutableMap.of("gh", "2", "name", "姓名-002", "deptName", "部门-002",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));
        list.add(ImmutableMap.of("gh", "3", "name", "姓名-003", "deptName", "部门-003",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));
        list.add(ImmutableMap.of("gh", "4", "name", "姓名-004", "deptName", "部门-004",
                "attachUrls", Arrays.asList("https://i2.hdslb.com/bfs/archive/c268dd8a28a29ee6872375c9a4385c35d789a5db.jpg@320w_200h")));

        // 创建工作薄
        // xlsx格式  XSSF
        Workbook workbook = new XSSFWorkbook();

        // 创建工作表
        Sheet sheet = workbook.createSheet("用户数据");
        // 设置列宽 设置默认宽度
        sheet.setDefaultColumnWidth(25);

        // 设置表头 简单点，不设置样式了
        String[] headers = new String[]{"工号", "姓名", "部门", "附件"};
        // 创建第一行
        Row titleRow = sheet.createRow(0);
        Cell cell = null;
        for (int i = 0; i < headers.length; i++) {
            // 创建单元格
            cell = titleRow.createCell(i);
            cell.setCellValue(headers[i]);
        }

        long currentTimeMillis = System.currentTimeMillis();
        // 遍历集合数据，生成数据行
        if (CollectionUtils.isNotEmpty(list)) {
            // 从第2行开始
            int rowIndex = 1;
            Row row = null;
            for (Map<String, Object> item : list) {
                String gh = (String) item.get("gh");
                String name = (String) item.get("name");
                String deptName = (String) item.get("deptName");
                List<String> attachUrls = (List<String>) item.get("attachUrls");

                row = sheet.createRow(rowIndex);

                cell = row.createCell(0);
                cell.setCellValue(gh);

                cell = row.createCell(1);
                cell.setCellValue(name);

                cell = row.createCell(2);
                cell.setCellValue(deptName);

                cell = row.createCell(3);

                // Excel HYPERLINK 功能 https://zh-cn.extendoffice.com/excel/functions/excel-hyperlink-function.html
                // 公式语法 HYPERLINK (link_location, [friendly_name])
                // Link_location: 这是必需的，是您要跳转到的给定文件的保存路径或网页的目标。
                // Friendly_name: 在公式单元格中显示的可选内容。
                // 如果参数以文本字符串形式提供，则用引号引起来，例如 Hyperlink(“#Sheet1!A1”, “A1 cell”)

                if (CollectionUtils.isNotEmpty(attachUrls)) {
                    for (int i = 0; i < attachUrls.size(); i++) {
                        String attachUrl = attachUrls.get(i);
                        // 附件目录 相对目录 和生成的excel同级
                        // 因为附件不止一个，所以直接打开附件目录即可
                        String codeLink = getFilePath(deptName, name, gh, null, currentTimeMillis, 3);
                        // 超链接描述
                        String code = "打开附件目录";
                        // 生成的超链接不带蓝色下划线样式
                        // cell.setCellFormula("HYPERLINK(\"" + codeLink + "\",\"" + code + "\")");
                        // java.lang.IllegalArgumentException: can't parse argument number  {}里面必须写数字 序号
                        String formula = MessageFormat.format("HYPERLINK(\"{0}\",\"{1}\")", codeLink, code);
                        cell.setCellFormula(formula);

                        // 手动给超链接添加样式 https://blog.csdn.net/nhx900317/article/details/121489191
                        // 创建单元格样式
                        CellStyle cellStyle = workbook.createCellStyle();
                        // 不直接使用getCellStyle()，用cloneStyleFrom就能实现保持原有样式
                        cellStyle.cloneStyleFrom(cell.getCellStyle());
                        // 设置字体
                        Font font = workbook.createFont();
                        font.setColor(IndexedColors.BLUE.getIndex());
                        font.setUnderline((byte) 1);
                        cellStyle.setFont(font);
                        // 设置单元格样式
                        cell.setCellStyle(cellStyle);

                        String fileName = "附件_" + (i+1) + ".jpg";
                        // 获取附件保存地址
                        String filePath = getFilePath(deptName, name, gh, fileName, currentTimeMillis, 2);
                        // 要注意的点就是，如果附件太多，下载附件可能需要很久很久
                        // 不能实现前端立即下载，需要后台生成，然后消息通知之类的
                        // 如果把附件直接保存在本地服务器之上，速度会快一点
                        saveFile(attachUrl, filePath);
                    }
                }

                rowIndex++;
            }
        }

        // 获取excel保存地址
        String excelFilePath = getFilePath(null, null, null,
                "用户数据.xlsx", currentTimeMillis, 1);
        // 生成excel
        ExcelUtil.saveExcelFile(workbook, excelFilePath);
        workbook.close();

        // 打成zip
        File desc = new File(excelFilePath);
        // excel保存临时目录
        File dir = desc.getParentFile();

        // SimpleDateFormat format1 = new SimpleDateFormat("yyyy年MM月dd日 HH时mm分ss秒");
        // java.lang.IllegalArgumentException: Illegal pattern character 'i'  后面的.zip不能用
        // SimpleDateFormat dateFormat = new SimpleDateFormat("用户数据(yyyy-MM-dd_HH-mm-ss).zip");
        SimpleDateFormat dateFormat = new SimpleDateFormat("用户数据(yyyy-MM-dd_HH-mm-ss)");

        String zipFilePath = dir.getParentFile().getParentFile().getAbsolutePath() +
                File.separator + dateFormat.format(new Date()) + ".zip";

        try (FileOutputStream out = new FileOutputStream(zipFilePath)) {
            ZipUtils.toZip(dir, out, true);
            // 删除临时文件夹
            ZipUtils.delFile(dir.getParentFile());
        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    /**
     * 获取文件路径
     *
     * @param deptName
     * @param name
     * @param gh
     * @param currentTimeMillis
     * @param fileName          文件名
     * @param fileType          1-excel，2-附件，3-附件保存目录(相对路径)
     */
    private static String getFilePath(String deptName, String name, String gh, String fileName, long currentTimeMillis, int fileType) {
        StringBuilder filePath = new StringBuilder();
        if (fileType != 3) {
            // 绝对路径
            String osName = System.getProperties().getProperty("os.name");
            if (osName.contains("Linux")) {
                filePath.append(rootDirLinux);
            } else {
                filePath.append(rootDirWin);
            }
            filePath.append(File.separator + "temp" + currentTimeMillis);
            filePath.append(File.separator + "用户数据");
            // 下方相对路径不需要 / 开头
            filePath.append(File.separator);
        }

        if (fileType == 1) {
            // 获取excel生成路径
            filePath.append(fileName);
            return filePath.toString();
        }
        // 获取当前用户附件保存路径
        filePath.append("附件");
        filePath.append(File.separator + deptName);
        filePath.append(File.separator + name + "(" + gh + ")");

        if (fileType == 3) {
            // 附件保存目录(相对路径)
            return filePath.toString();
        }

        filePath.append(File.separator + fileName);
        return filePath.toString();
    }

    /**
     * 下载网络附件，保存到临时目录
     *
     * @param urlString
     * @param filePath
     */
    private static String saveFile(String urlString, String filePath) {
        File desc = new File(filePath);
        File dir = desc.getParentFile();
        if (!dir.exists()) {
            dir.mkdirs();
        }

        boolean rs = false;
        if (!desc.exists()) {
            ReadableByteChannel rbc = null;
            FileOutputStream fos = null;
            try {
                URL website = new URL(urlString);
                HttpURLConnection urlCon = (HttpURLConnection) website.openConnection();
                // 指定超时时间，不指定可能会无限等待
                urlCon.setConnectTimeout(180000);
                urlCon.setReadTimeout(180000);

                rbc = Channels.newChannel(urlCon.getInputStream());
                fos = new FileOutputStream(desc);
                fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
                rs = true;
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (fos != null) {
                    try {
                        fos.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if (rbc != null) {
                    try {
                        rbc.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        } else {
            rs = true;
        }

        // 输出绝对路径
        return rs ? desc.getAbsolutePath() : null;
    }

}